package com.lcq.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.lcq.common.to.SkuSearchInfoTo;
import com.lcq.common.utils.R;
import com.lcq.gulimall.search.config.ElasticSearchConfig;
import com.lcq.gulimall.search.constant.ElasticConstant;
import com.lcq.gulimall.search.feign.ProductFeign;
import com.lcq.gulimall.search.service.MallSearchService;
import com.lcq.gulimall.search.vo.SearchParam;
import com.lcq.gulimall.search.vo.SearchResult;
import com.mysql.cj.util.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    RestHighLevelClient client;

    @Autowired
    ProductFeign productFeign;
    @Override
    public SearchResult search(SearchParam searchParam) {

        /**
         *
         */
        SearchRequest request = new SearchRequest();
        request.indices(ElasticConstant.PRODUCT_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(buildQueryBuilder(searchParam));
        buildAggregation(searchSourceBuilder);
        if(searchParam.getSort()!=null){
            searchSourceBuilder.sort(buildSortBuilder(searchParam.getSort()));
        }

        //分页
        if (searchParam.getPageNum()!=null){
            searchSourceBuilder.from((searchParam.getPageNum()-1)*ElasticConstant.PRODUCT_PAGE_SIZE);
        }
        searchSourceBuilder.size(ElasticConstant.PRODUCT_PAGE_SIZE);

        //高亮
        searchSourceBuilder.highlighter(buildHighlightBuilder(searchParam));

        System.out.println("searchSource:"+searchSourceBuilder.toString());
        request.source(searchSourceBuilder);
        SearchResult searchResult = null;
        try {
            SearchResponse searchResponse = client.search(request, ElasticSearchConfig.COMMON_OPTIONS);
            searchResult = buildSearchResult(searchParam,searchResponse);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            return searchResult;
        }
    }

    private SearchResult buildSearchResult(SearchParam searchParam, SearchResponse searchResponse) {
        SearchResult searchResult = new SearchResult();
        SearchHits hits = searchResponse.getHits();
        long totalHit = hits.getTotalHits().value;
        searchResult.setTotal(totalHit);
        int pageSize = ElasticConstant.PRODUCT_PAGE_SIZE;
        searchResult.setTotalPages((int)totalHit%pageSize==0?(int)totalHit/pageSize:(int)totalHit/pageSize+1);
        List<SkuSearchInfoTo> productsList = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            SkuSearchInfoTo info = JSON.parseObject(hit.getSourceAsString(), SkuSearchInfoTo.class);
            if(!StringUtils.isNullOrEmpty(searchParam.getKeyword())){
                info.setSkuTitle(hit.getHighlightFields().get("skuTitle").getFragments()[0].string());
            }
            productsList.add(info);
        }
        searchResult.setProducts(productsList);

        ParsedLongTerms brandAggTerms = searchResponse.getAggregations().get("brand_agg");
        List<SearchResult.BrandVo> brandVos = new ArrayList<>();
        for (Terms.Bucket bucket : brandAggTerms.getBuckets()) {
            ParsedLongTerms.ParsedBucket b = (ParsedLongTerms.ParsedBucket) bucket;
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            brandVo.setBrandId((Long) b.getKey());
            ParsedStringTerms brandNameTerms = b.getAggregations().get("brand_name_agg");
            brandVo.setBrandName(brandNameTerms.getBuckets().get(0).getKey().toString());
            ParsedStringTerms brandImgTerms = b.getAggregations().get("brand_img_agg");
            brandVo.setBrandImg(brandImgTerms.getBuckets().get(0).getKey().toString());
            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);

        ParsedLongTerms catalogAggTerms = searchResponse.getAggregations().get("catalog_agg");
        List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
        for (Terms.Bucket bucket : catalogAggTerms.getBuckets()) {
            ParsedLongTerms.ParsedBucket b = (ParsedLongTerms.ParsedBucket) bucket;
            SearchResult.CatalogVo brandVo = new SearchResult.CatalogVo();
            brandVo.setCatalogId((Long) b.getKey());
            ParsedStringTerms brandNameTerms = b.getAggregations().get("catalog_name_agg");
            brandVo.setCatalogName(brandNameTerms.getBuckets().get(0).getKey().toString());
            catalogVos.add(brandVo);
        }
        searchResult.setCatalogs(catalogVos);

        ParsedNested attrAgg = searchResponse.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAggTerms = attrAgg.getAggregations().get("attr_id_agg");
        List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
        for (Terms.Bucket bucket : attrIdAggTerms.getBuckets()) {
            ParsedLongTerms.ParsedBucket b = (ParsedLongTerms.ParsedBucket) bucket;
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            attrVo.setAttrId((Long) b.getKey());
            ParsedStringTerms attrNameTerms = b.getAggregations().get("attr_name_agg");
            attrVo.setAttrName(attrNameTerms.getBuckets().get(0).getKey().toString());
            ParsedStringTerms attrValueTerms = b.getAggregations().get("attr_value_agg");
            List<String> attrValue = new ArrayList<>();
            for (Terms.Bucket attrValueTermsBucket : attrValueTerms.getBuckets()) {
                try {
                    ParsedStringTerms.ParsedBucket bb = (ParsedStringTerms.ParsedBucket) attrValueTermsBucket;
                    attrValue.add(bb.getKey().toString());
                }catch (Exception e){
                    throw e;
                }
            }
            attrVo.setAttrValue(attrValue);
            attrVoList.add(attrVo);
        }
        searchResult.setAttrs(attrVoList);

        searchResult.setPageNum(searchParam.getPageNum());
//        searchResult.setAttrIds();
//        searchResult.setPageNavs();
        searchParam.getBrandId();
        List<SearchResult.NavVo> navVos = new ArrayList<>();

        List<Long> brandIds = searchParam.getBrandId();
        if(brandIds!=null&&brandIds.size()!=0){
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setName("品牌");
            R  r = productFeign.brandName(brandIds.get(0));
            navVo.setNavValue((String) r.get("name"));
            String url = searchParam.get_queryString();
            String removeStr="brandId";
            String regex = "(&)?("+removeStr+"=)([^&]*)";
            url = removeQueryStringParam(url,regex);
            navVo.setLink(url);
            navVos.add(navVo);
        }

        List<String> attrs = searchParam.getAttrs();
        if(attrs!=null&&attrs.size()!=0){
            attrs.stream().forEach(attr->{
                String[] attrSplit = attr.split("_");
                if(attrSplit.length==2){
                    String attrId = attrSplit[0];
                    String[] attrValues = attrSplit[1].split(":");
                    SearchResult.NavVo navVo = new SearchResult.NavVo();
                    navVo.setName((String) productFeign.attrName(Long.parseLong(attrId)).get("name"));
                    navVo.setNavValue(attrValues[0]);
                    String url = searchParam.get_queryString();
                    String removeStr="attrs="+attrId;
                    url = removeQueryStringParam(url,"(&)?("+removeStr+")([^&]*)");
                    navVo.setLink(url);
                    navVos.add(navVo);
                }
            });
        }
        searchResult.setNavs(navVos);
        return searchResult;
    }

    private String removeQueryStringParam(String queryString, String regex){
        String url = "";
        url = queryString.replaceAll(regex,"");
        if (url.startsWith("&")){
            url=url.replaceFirst("&","");
        }
        url = "http://search.gulimall.com/search.html?"+url;
        return url;
    }

    private QueryBuilder buildQueryBuilder(SearchParam searchParam){
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(!StringUtils.isNullOrEmpty(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }

        if (searchParam.getBrandId()!=null){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }

        if(searchParam.getCatalog3Id()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
        }

        if(searchParam.getHasStock()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock()==1));
        }

        /**
         *  range  _500 100_500 100_
         */
        if(searchParam.getSkuPrice()!=null){
            String[] prices = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            if(prices.length==2){
                rangeQuery.gte(prices[0]);
                rangeQuery.lte(prices[1]);
            }else if (prices.length==1){
                if (searchParam.getSkuPrice().startsWith("_")){
                    rangeQuery.lte(prices[0]);
                }else if (searchParam.getSkuPrice().endsWith("_")){
                    rangeQuery.gte(prices[0]);
                }
            }
            boolQueryBuilder.filter(rangeQuery);
        }

        if(searchParam.getAttrs()!=null&&searchParam.getAttrs().size()!=0){
            /**
             *  attrs=1_5寸:7寸&attrs=2_16G:8G
             */
            List<String> attrs = searchParam.getAttrs();

            attrs.forEach(attr->{
                String[] attrSplit = attr.split("_");
                if(attrSplit.length==2){
                    String attrId = attrSplit[0];
                    String[] attrValues = attrSplit[1].split(":");
                    BoolQueryBuilder nestedBoolQueryBuilder = QueryBuilders.boolQuery();
                    nestedBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                    nestedBoolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", nestedBoolQueryBuilder, ScoreMode.None));
                }
            });
        }
        return boolQueryBuilder;
    }

    private void buildAggregation(SearchSourceBuilder searchSourceBuilder){
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId").size(10);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(10));
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(10));
        searchSourceBuilder.aggregation(brandAgg);

        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(10);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(10));
        searchSourceBuilder.aggregation(catalogAgg);

        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(10));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(10));
        attrAgg.subAggregation(attr_id_agg);
        searchSourceBuilder.aggregation(attrAgg);
    }

    private SortBuilder buildSortBuilder(String sortStr){
        /**
         * sort=hostScore_desc/asc
         */
        String[] sortSplit = sortStr.split("_");
        String sortField = sortSplit[0];
        SortOrder order =  sortSplit[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
        return SortBuilders.fieldSort(sortField).order(order);
    }

    private HighlightBuilder buildHighlightBuilder(SearchParam searchParam){
        HighlightBuilder highlightBuilder = null;
        if (searchParam.getKeyword()!=null){
            highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
        }
        return highlightBuilder;
    }
}
